#pragma once

int CastCallable(lua_State* L);
int CastCallableWithDtor(lua_State* L);

#define ScriptCallableInitializer(Fx) \
	ScriptCallable(Fx &&fx) : fx(std::forward<Fx>(fx)) {} \
	ScriptCallable(const Fx &fx) : fx(fx) {} \
	Fx fx;
#define ScriptCallableUpvalue(Fx, L, index) \
	lua::read<const Fx &>::invoke(L, lua_upvalueindex(index))
#define ScriptCallableInvoke(Fx, Args, L, Indexes) \
	ScriptCallableUpvalue(Fx, L, 1)(lua::read<Args>::invoke(L, Indexes + 1)...)

template <typename Fx, typename T>
struct ScriptCallable {};

template <typename Fx, typename RVal, typename... Args>
struct ScriptCallable<Fx, RVal(Args...)> {
	static int invoke(lua_State *L) {
		invoke(L, std::index_sequence_for<Args...>{});
		return 1;
	}
	template<std::size_t... Indexes>
	static void invoke(lua_State *L, std::index_sequence<Indexes...>) {
		lua::push<RVal>::invoke(L, ScriptCallableInvoke(Fx, Args, L, Indexes));
	}
	ScriptCallableInitializer(Fx);
};

template <typename Fx, typename... Args>
struct ScriptCallable<Fx, void(Args...)> {
	static int invoke(lua_State *L) {
		invoke(L, std::index_sequence_for<Args...>{});
		return 0;
	}
	template<std::size_t... Indexes>
	static void invoke(lua_State *L, std::index_sequence<Indexes...>) {
		ScriptCallableInvoke(Fx, Args, L, Indexes);
	}
	ScriptCallableInitializer(Fx);
};

template <typename T, typename Fx>
ScriptCallable<typename std::remove_reference<Fx>::type, T> CastScriptCallable(Fx &&fx) {
	return {std::forward<Fx>(fx)};
}

template <typename T>
ScriptCallable<std::function<T>, T> CastScriptCallable(std::function<T> &&fx) {
	return {std::move(fx)};
}
template <typename T>
ScriptCallable<std::function<T>, T> CastScriptCallable(const std::function<T> &fx) {
	return {fx};
}

namespace lua {

	template<typename Fx, typename RVal, typename... Args> struct push<ScriptCallable<Fx, RVal(Args...)>> {
		static void invoke(lua_State *L, ScriptCallable<Fx, RVal(Args...)> val) {
			push<Fx &&>::invoke(L, std::move(val.fx));
			lua_pushcclosure(L, ScriptCallable<Fx, RVal(Args...)>::invoke, 1);
		}
	};
	template<typename Fx, typename RVal, typename... Args> struct push<const ScriptCallable<Fx, RVal(Args...)>> {
		static void invoke(lua_State *L, ScriptCallable<Fx, RVal(Args...)> val) {
			push<Fx &&>::invoke(L, std::move(val.fx));
			lua_pushcclosure(L, ScriptCallable<Fx, RVal(Args...)>::invoke, 1);
		}
	};
	template<typename Fx, typename RVal, typename... Args> struct push<ScriptCallable<Fx, RVal(Args...)> &> {
		static void invoke(lua_State *L, const ScriptCallable<Fx, RVal(Args...)> &val) {
			push<const Fx &>::invoke(L, val.fx);
			lua_pushcclosure(L, ScriptCallable<Fx, RVal(Args...)>::invoke, 1);
		}
	};
	template<typename Fx, typename RVal, typename... Args> struct push<const ScriptCallable<Fx, RVal(Args...)> &> {
		static void invoke(lua_State *L, const ScriptCallable<Fx, RVal(Args...)> &val) {
			push<const Fx &>::invoke(L, val.fx);
			lua_pushcclosure(L, ScriptCallable<Fx, RVal(Args...)>::invoke, 1);
		}
	};
	template<typename Fx, typename RVal, typename... Args> struct push<ScriptCallable<Fx, RVal(Args...)> &&> {
		static void invoke(lua_State *L, ScriptCallable<Fx, RVal(Args...)> &&val) {
			push<Fx &&>::invoke(L, std::move(val.fx));
			lua_pushcclosure(L, ScriptCallable<Fx, RVal(Args...)>::invoke, 1);
		}
	};
	template<typename Fx, typename RVal, typename... Args> struct push<const ScriptCallable<Fx, RVal(Args...)> &&> {
		static void invoke(lua_State *L, const ScriptCallable<Fx, RVal(Args...)> &&val) {
			push<const Fx &&>::invoke(L, std::move(val.fx));
			lua_pushcclosure(L, ScriptCallable<Fx, RVal(Args...)>::invoke, 1);
		}
	};

}
